Skip to content

feat: add .md endpoint for API reference pages#57

Merged
rsbh merged 1 commit into
mainfrom
feat/api-md-route
May 13, 2026
Merged

feat: add .md endpoint for API reference pages#57
rsbh merged 1 commit into
mainfrom
feat/api-md-route

Conversation

@rsbh
Copy link
Copy Markdown
Member

@rsbh rsbh commented May 13, 2026

Summary

  • New route handler at server/routes/apis/[...slug].md.ts generates markdown from OpenAPI spec
  • Includes: authorization, path/query parameters, request body with field table + JSON example, response schemas per status code, cURL snippet
  • Existing [...slug].md.ts skips /apis/ paths to avoid conflicts
  • Works with existing "Copy as MD" and "Open in ChatGPT/Claude" navbar buttons

Test plan

  • bun run build:cli && bun run dev:examples:basic
  • Visit /apis/{spec}/{operationId}.md — returns markdown
  • Click "Copy as MD" on API page — copies generated markdown
  • Click "View MD" — opens markdown in new tab
  • "Open in ChatGPT/Claude" sends correct .md URL
  • Docs pages .md URLs still work unchanged

🤖 Generated with Claude Code

Generate markdown from OpenAPI spec at /apis/{spec}/{operationId}.md
with authorization, parameters, request body, response schemas,
JSON examples, and cURL snippet.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
chronicle Ready Ready Preview, Comment May 13, 2026 4:11am

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 13, 2026

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Introduced automatic API documentation generation from OpenAPI specifications, including operation details, parameters, request/response schemas, authorization requirements, and executable cURL examples.

Walkthrough

This PR adds an OpenAPI-to-Markdown documentation generation feature. A route guard prevents the default markdown handler from processing /apis/ requests, and a new dedicated handler generates formatted API operation documentation from OpenAPI specs with parameters, schemas, examples, and executable cURL snippets.

Changes

API Documentation Generation

Layer / File(s) Summary
Route filtering and entry point
packages/chronicle/src/server/routes/[...slug].md.ts, packages/chronicle/src/server/routes/apis/[...slug].md.ts
Early-return guard prevents the catch-all markdown handler from processing /apis/ paths. New API route handler extracts slug, loads OpenAPI specs, locates matching operations, and returns generated Markdown or 404.
Markdown generation from OpenAPI
packages/chronicle/src/server/routes/apis/[...slug].md.ts
generateApiMarkdown transforms an OperationObject into a formatted Markdown document with authorization, path/query parameters, flattened request body schema field tables, response status sections with schemas and examples, and a cURL command.
Field table rendering helpers
packages/chronicle/src/server/routes/apis/[...slug].md.ts
renderFieldTable and renderResponseFieldTable recursively flatten nested schema fields into Markdown table rows with depth-based indentation for children.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • raystack/chronicle#46: Modifies the same Nitro markdown route handler to return Response objects; this PR adds an /apis/ guard to the existing handler.

Suggested reviewers

  • rohanchkrabrty
  • rohilsurana
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: add .md endpoint for API reference pages' accurately summarizes the main change: adding a new markdown endpoint for API reference documentation.
Description check ✅ Passed The description is directly related to the changeset, providing a clear summary of the new route handler, its features, integration with existing UI, and a concrete test plan.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/api-md-route

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
packages/chronicle/src/server/routes/apis/[...slug].md.ts (3)

95-96: 💤 Low value

Content-Type selection uses first key.

When multiple content types are defined in requestBody.content, this implementation renders the first one from Object.keys(). While modern JavaScript engines preserve insertion order, explicitly preferring application/json (if present) might be more predictable for API documentation.

♻️ Proposed refinement
-    const contentType = Object.keys(requestBody.content)[0]
+    const contentTypes = Object.keys(requestBody.content)
+    const contentType = contentTypes.includes('application/json') 
+      ? 'application/json' 
+      : contentTypes[0]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/chronicle/src/server/routes/apis/`[...slug].md.ts around lines 95 -
96, The code currently picks the content type by taking the first key from
requestBody.content (contentType = Object.keys(requestBody.content)[0]); change
this to explicitly prefer "application/json" when present (e.g., if
("application/json" in requestBody.content) contentType = "application/json";
else contentType = Object.keys(requestBody.content)[0]) and then derive schema
the same way (schema = contentType ? requestBody.content[contentType]?.schema as
OpenAPIV3.SchemaObject : undefined), so that the variable contentType and schema
resolution in this module prefer application/json but still fall back to the
original behavior.

167-181: 💤 Low value

Consider consolidating field table rendering.

renderFieldTable and renderResponseFieldTable are nearly identical except for column count. You could merge them with a boolean flag (e.g., includeRequired) or column-spec parameter to reduce duplication.

♻️ Example consolidation
function renderFieldTable(
  fields: SchemaField[], 
  lines: string[], 
  depth: number,
  includeRequired: boolean = true
) {
  const indent = '  '.repeat(depth)
  for (const f of fields) {
    const requiredCol = includeRequired ? ` | ${f.required ? 'Yes' : 'No'}` : ''
    lines.push(`| ${indent}\`${f.name}\` | ${f.type}${requiredCol} | ${f.description ?? ''} |`)
    if (f.children) renderFieldTable(f.children, lines, depth + 1, includeRequired)
  }
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/chronicle/src/server/routes/apis/`[...slug].md.ts around lines 167 -
181, renderFieldTable and renderResponseFieldTable duplicate logic; consolidate
them into a single function (e.g., renderFieldTable) that accepts an extra
parameter (boolean includeRequired or a columns spec) to control whether the
"Required" column is emitted, update all recursive calls (renderFieldTable →
renderFieldTable(..., includeRequired)) and replace calls to
renderResponseFieldTable to call the unified function with
includeRequired=false; ensure the emitted Markdown row string builds the
required column conditionally so table column counts remain correct.

19-21: ⚖️ Poor tradeoff

Consider caching config and API specs.

Both loadConfig() and loadApiSpecs() lack internal caching and perform operations on every invocation: loadConfig() re-parses the config, while loadApiSpecs() performs disk I/O (fs.readFile) and re-parses YAML/JSON specs. For frequently accessed endpoints, caching these results at the handler or application level could improve performance.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/chronicle/src/server/routes/apis/`[...slug].md.ts around lines 19 -
21, The handler repeatedly calls loadConfig() and loadApiSpecs(), causing
re-parsing and disk I/O on each request; modify the code to cache their results
(e.g., module-level memoization or a simple in-memory cache with optional
TTL/invalidation) so subsequent calls return the cached config and parsed specs
instead of re-reading files; specifically, add caching logic around loadConfig()
and loadApiSpecs() used before calling findApiOperation(specs, slug), ensure the
cache can be invalidated when config or spec files change (or use a short TTL)
and keep function signatures unchanged so callers like the route handler still
call loadConfig() and loadApiSpecs() but get cached results.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/chronicle/src/server/routes/apis/`[...slug].md.ts:
- Around line 95-96: The code currently picks the content type by taking the
first key from requestBody.content (contentType =
Object.keys(requestBody.content)[0]); change this to explicitly prefer
"application/json" when present (e.g., if ("application/json" in
requestBody.content) contentType = "application/json"; else contentType =
Object.keys(requestBody.content)[0]) and then derive schema the same way (schema
= contentType ? requestBody.content[contentType]?.schema as
OpenAPIV3.SchemaObject : undefined), so that the variable contentType and schema
resolution in this module prefer application/json but still fall back to the
original behavior.
- Around line 167-181: renderFieldTable and renderResponseFieldTable duplicate
logic; consolidate them into a single function (e.g., renderFieldTable) that
accepts an extra parameter (boolean includeRequired or a columns spec) to
control whether the "Required" column is emitted, update all recursive calls
(renderFieldTable → renderFieldTable(..., includeRequired)) and replace calls to
renderResponseFieldTable to call the unified function with
includeRequired=false; ensure the emitted Markdown row string builds the
required column conditionally so table column counts remain correct.
- Around line 19-21: The handler repeatedly calls loadConfig() and
loadApiSpecs(), causing re-parsing and disk I/O on each request; modify the code
to cache their results (e.g., module-level memoization or a simple in-memory
cache with optional TTL/invalidation) so subsequent calls return the cached
config and parsed specs instead of re-reading files; specifically, add caching
logic around loadConfig() and loadApiSpecs() used before calling
findApiOperation(specs, slug), ensure the cache can be invalidated when config
or spec files change (or use a short TTL) and keep function signatures unchanged
so callers like the route handler still call loadConfig() and loadApiSpecs() but
get cached results.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bd59ef61-cda4-4c75-84c2-7676fe52a75a

📥 Commits

Reviewing files that changed from the base of the PR and between 4b4857e and 599b922.

📒 Files selected for processing (2)
  • packages/chronicle/src/server/routes/[...slug].md.ts
  • packages/chronicle/src/server/routes/apis/[...slug].md.ts

@rsbh rsbh merged commit a3c840e into main May 13, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants